package com.solome.vertx;

import com.alibaba.fastjson2.JSON;
import com.solome.vertx.annotation.*;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Function;

/**
 * Abstract base class for handling HTTP requests in a Vert.x application.
 * 抽象基类，用于在 Vert.x 应用中处理 HTTP 请求。 Provides routing, token validation, and
 * response handling with caching for request mappings.
 * 提供路由、令牌验证和响应处理功能，并通过缓存优化请求映射性能。
 */
@Slf4j
public abstract class HttpAction {

	@Getter
	private final Map<String, RequestCache> cache = new HashMap<>(); // Cache for routing and method mappings / 路由与方法的缓存

	@Getter
	private String router; // Base path for the controller / 控制器基础路径

	@Getter
	@Setter
	private Vertx vertx; // Vert.x instance / Vert.x 实例

	private static final String CORS_METHODS = "GET, POST, OPTIONS";
	private static final String CORS_HEADERS = "Content-Type, Authorization, Access-Token";
	private static final String CORS_MAX_AGE = "3600";
	private static final String ACCESS_TOKEN = "Access-Token";

	private static final Map<Parameter, Param> paramCache = new WeakHashMap<>(); // Cache for parameter annotations /
																					// 参数注解缓存
	private static final Map<Parameter, Class<?>> typeCache = new WeakHashMap<>(); // Cache for parameter types / 参数类型缓存

	/**
	 * Initializes the method mapping cache based on annotations.
	 * 初始化方法映射缓存，根据注解加载路由与方法的对应关系。
	 */
	public void loadCache() {
		RequestMapping requestMapping = this.getClass().getAnnotation(RequestMapping.class);
		if (requestMapping == null) {
			return;
		}
		this.router = normalizePath(requestMapping.value());
		for (Method method : this.getClass().getDeclaredMethods()) {
			Mapping mapping = method.getAnnotation(Mapping.class);
			if (mapping != null) {
				String key = mapping.value() + "_" + mapping.method().name();
				cache.put(key, new RequestCache(mapping, method));
			}
		}
		initialized();
	}

	/**
	 * Normalizes the path to start with "/" and not end with "/". 标准化路径，确保以 "/"
	 * 开头且不以 "/" 结尾。
	 */
	private String normalizePath(String path) {
		path = path.startsWith("/") ? path : "/" + path;
		return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
	}

	/**
	 * Finds the matching route cache based on the request URI and HTTP method. 根据请求
	 * URI 和 HTTP 方法查找匹配的路由缓存。
	 */
	private RequestCache getRouter(String router, HttpMethod method) {
		String cacheRouter = normalizePath(router);
		for (RequestCache rc : cache.values()) {
			if (isMethodMatch(rc, method) && isRouterMatch(rc.getMapping().value(), cacheRouter)) {
				return rc;
			}
		}
		return null;
	}

	/**
	 * Checks if the HTTP method matches the mapping. 检查 HTTP 方法是否与映射定义匹配。
	 */
	private boolean isMethodMatch(RequestCache rc, HttpMethod method) {
		com.solome.vertx.annotation.HttpMethod mappingMethod = rc.getMapping().method();
		return mappingMethod == com.solome.vertx.annotation.HttpMethod.ALL
				|| mappingMethod.name().equals(method.name());
	}

	/**
	 * Checks if the request route matches the cached route, supporting wildcards
	 * (*). 检查请求路由是否与缓存路由匹配，支持通配符（*）。
	 */
	private boolean isRouterMatch(String cacheRouter, String router) {
		if (cacheRouter.equals(router) || (router.isEmpty() && cacheRouter.isEmpty())) {
			return true;
		}
		return cacheRouter.endsWith("*") && router.startsWith(cacheRouter.substring(0, cacheRouter.length() - 1));
	}

	/**
	 * Handles HTTP requests, setting up CORS, routing, and responses. 处理 HTTP 请求，设置
	 * CORS、路由分发和响应。
	 */
	public void initHandle(RoutingContext context) {
		long startTime = System.currentTimeMillis();
		setupCorsHeaders(context);
		if (context.request().method() == HttpMethod.OPTIONS) {
			context.response().setStatusCode(200).end();
			return;
		}

		try {
			String router = extractRouter(context.request().path());
			RequestCache cache = getRouter(router, context.request().method());
			if (cache == null) {
				sendResponse(context, 404, "Resource not found");
				return;
			}

			if (!validateToken(context, cache)) {
				sendResponse(context, 401, "Unauthorized");
				return;
			}

			processRequestAsync(context, cache).onComplete(ar -> {
				if (ar.succeeded()) {
					if (cache.getResponseMethon() == null) {
						sendResponse(context, 200, ar.result());
					}
				} else {
					log.error("Request processing failed", ar.cause());
					sendResponse(context, 500, "Internal server error");
				}
				if (cache.getMapping().log()) {
					logRequest(context, startTime);
				}
			});
		} catch (Exception e) {
			log.error("Request processing failed", e);
			sendResponse(context, 500, "Internal server error");
		}
	}

	/**
	 * Extracts the relative route path from the full URI. 从完整 URI 中提取相对路由路径。
	 */
	private String extractRouter(String uri) {
		return uri.startsWith(router) ? uri.substring(router.length()) : uri;
	}

	/**
	 * Sets up CORS response headers. 配置 CORS 响应头。
	 */
	private void setupCorsHeaders(RoutingContext context) {
		HttpServerResponse response = context.response();
		response.putHeader("Access-Control-Allow-Origin", context.request().getHeader("Origin"))
				.putHeader("Access-Control-Allow-Methods", CORS_METHODS)
				.putHeader("Access-Control-Allow-Headers", CORS_HEADERS)
				.putHeader("Access-Control-Max-Age", CORS_MAX_AGE);
	}

	/**
	 * Validates if a token is required and present. 验证请求是否需要令牌且令牌是否有效。
	 */
	private boolean validateToken(RoutingContext context, RequestCache cache) {
		CheckToken classCheckToken = this.getClass().getAnnotation(CheckToken.class);
		CheckToken methodCheckToken = cache.getCheckToken();
		boolean requiresToken = (classCheckToken != null && classCheckToken.value())
				|| (methodCheckToken != null && methodCheckToken.value());

		if (!requiresToken) {
			log.debug("Token validation not required.");
			return true;
		}

		if (!context.request().headers().contains(ACCESS_TOKEN)) {
			log.warn("Missing Access-Token in request headers.");
			return false;
		}

		String token = context.request().headers().get(ACCESS_TOKEN);
		if (token == null || token.isEmpty()) {
			log.warn("Access-Token is empty.");
			return false;
		}

		SessionKey key = new SessionKey(token);
		boolean isValid = key.getUId() > 0;
		if (!isValid) {
			log.warn("Invalid token: UID <= 0 for token {}", token);
		}
		return isValid;
	}

	/**
	 * Asynchronously processes the request and invokes the mapped method.
	 * 异步处理请求并调用映射方法。
	 */
	private Future<Object> processRequestAsync(RoutingContext context, RequestCache cache) {
		Promise<Object> promise = Promise.promise();
		StringBuilder logBuilder = cache.getMapping().log() ? new StringBuilder() : null;

		buildMethodParametersAsync(context, cache, logBuilder).onComplete(ar -> {
			if (ar.succeeded()) {
				try {
					Object result = cache.getMethod().invoke(this, ar.result());
					if (logBuilder != null && result != null) {
						logBuilder.append(":result=").append(JSON.toJSONString(result));
					}
					promise.complete(result);
				} catch (InvocationTargetException | IllegalAccessException e) {
					promise.fail(e);
				}
			} else {
				promise.fail(ar.cause());
			}
		});
		return promise.future();
	}

	/**
	 * Asynchronously builds method parameters. 异步构建方法调用所需的参数。
	 */
	private Future<Object[]> buildMethodParametersAsync(RoutingContext context, RequestCache cache,
			StringBuilder logBuilder) {
		Promise<Object[]> promise = Promise.promise();
		Parameter[] parameters = cache.getMethod().getParameters();
		Object[] params = new Object[parameters.length];
		String body = getRequestBody(context);

		if (logBuilder != null) {
			logBuilder.append(body);
		}

		resolveParametersAsync(context, parameters, body, params, 0).onComplete(ar -> {
			if (ar.succeeded()) {
				promise.complete(params);
			} else {
				promise.fail(ar.cause());
			}
		});
		return promise.future();
	}

	/**
	 * Recursively resolves parameters asynchronously. 递归异步解析参数列表。
	 */
	private Future<Void> resolveParametersAsync(RoutingContext context, Parameter[] parameters, String body,
			Object[] params, int index) {
		if (index >= parameters.length) {
			return Future.succeededFuture();
		}

		Promise<Void> promise = Promise.promise();
		resolveParameterAsync(parameters[index], context, body).onComplete(ar -> {
			if (ar.succeeded()) {
				params[index] = ar.result();
				resolveParametersAsync(context, parameters, body, params, index + 1).onComplete(promise);
			} else {
				promise.fail(ar.cause());
			}
		});
		return promise.future();
	}

	/**
	 * Asynchronously resolves a single parameter value. 异步解析单个参数值。
	 */
	private Future<Object> resolveParameterAsync(Parameter parameter, RoutingContext context, String body) {
		Promise<Object> promise = Promise.promise();
		try {
			Class<?> paramType = typeCache.computeIfAbsent(parameter, Parameter::getType);
			Param param = paramCache.computeIfAbsent(parameter, p -> p.getAnnotation(Param.class));

			if (param != null) {
				String paramName = param.value();
				String val = context.request().params().get(paramName);

				if (val == null || val.trim().isEmpty()) {
					val = param.defaultValue();
					if (val.isEmpty()) {
						throw new IllegalArgumentException("Missing parameter: " + paramName);
					}
				}

				if (!param.pattern().isEmpty() && !val.matches(param.pattern())) {
					throw new IllegalArgumentException("Invalid parameter format: " + paramName);
				}

				if (paramType == List.class) {
					String value = context.request().params().get(paramName);
					if (value == null || value.isEmpty()) {
						throw new IllegalArgumentException("Empty list parameter: " + paramName);
					}
					List<Object> result = new ArrayList<>();
					ParameterizedType parameterizedType = (ParameterizedType) parameter.getParameterizedType();
					Class<?> genericType = (Class<?>) parameterizedType.getActualTypeArguments()[0];
					Function<String, Object> converter = ConverterRegistry.getConverter(genericType);
					if (converter != null) {
						String[] values = value.split(param.split());
						for (String v : values) {
							try {
								Object converted = converter.apply(v);
								validateNumberRange(param, paramName, converted);
								result.add(converted);
							} catch (Exception e) {
								throw new IllegalArgumentException(
										"Failed to convert parameter: " + paramName + ", value: " + v, e);
							}
						}
						promise.complete(result);
						return promise.future();
					}
					promise.complete(result);
					return promise.future();
				}

				Function<String, Object> converter = ConverterRegistry.getConverter(paramType);
				if (converter != null) {
					try {
						Object result = converter.apply(val);
						validateNumberRange(param, paramName, result);
						promise.complete(result);
					} catch (Exception e) {
						throw new IllegalArgumentException(
								"Failed to convert parameter: " + paramName + ", value: " + val, e);
					}
				} else {
					promise.complete(val);
				}
				return promise.future();
			}

			if (paramType == HttpServerRequest.class) {
				promise.complete(context.request());
			} else if (paramType == HttpServerResponse.class) {
				promise.complete(context.response());
			} else if (paramType == RoutingContext.class) {
				promise.complete(context);
			} else if (paramType == SessionKey.class) {
				String token = context.request().headers().get(ACCESS_TOKEN);
				if (token == null || token.trim().isEmpty()) {
					throw new IllegalArgumentException("Missing access token");
				}
				promise.complete(new SessionKey(token));
			} else if (paramType == String.class) {
				promise.complete(body != null ? body : "");
			} else if (body != null && !body.trim().isEmpty()) {
				try {
					promise.complete(JSON.parseObject(body, paramType));
				} catch (Exception e) {
					throw new IllegalArgumentException("Failed to parse JSON: " + paramType.getName(), e);
				}
			} else {
				promise.complete(null);
			}
		} catch (Exception e) {
			log.error("Failed to resolve parameter: {}", parameter.getName(), e);
			promise.fail(e);
		}
		return promise.future();
	}

	/**
	 * Validates the range of numeric parameters. 验证数值参数的范围。
	 */
	private void validateNumberRange(Param param, String paramName, Object value) {
		if (value instanceof Number) {
			long num = ((Number) value).longValue();
			if (num < param.min() || num > param.max()) {
				throw new IllegalArgumentException(
						"Parameter out of range: " + paramName + ", range: [" + param.min() + ", " + param.max() + "]");
			}
		}
	}

	/**
	 * Retrieves the request body, handling GET and POST differently. 获取请求体内容，区分 GET
	 * 和 POST 请求的处理方式。
	 */
	private String getRequestBody(RoutingContext context) {
		if (context.request().method() == HttpMethod.GET) {
			Map<String, String> paramMap = new HashMap<>();
			context.request().params().forEach(entry -> paramMap.put(entry.getKey(), entry.getValue()));
			return JSON.toJSONString(paramMap);
		}
		return context.body() != null ? context.body().asString("UTF-8") : null;
	}

	/**
	 * Sends an HTTP response with status code and content. 发送 HTTP 响应，包含状态码和内容。
	 */
	private void sendResponse(RoutingContext context, int statusCode, Object result) {
		HttpServerResponse response = context.response();
		response.setStatusCode(statusCode);
		String content = formatResponse(result);
		response.putHeader("Content-Type", statusCode >= 400 ? "text/html" : "application/json").end(content, "UTF-8");
	}

	/**
	 * Formats the response content based on the result type. 根据结果类型格式化响应内容。
	 */
	private String formatResponse(Object result) {
		if (result == null) {
			return "ok";
		}
		if (result instanceof Number || result instanceof String) {
			return result.toString();
		}
		return JSON.toJSONString(result);
	}

	/**
	 * Logs the request details, including duration and headers.
	 * 记录请求日志，包含耗时、请求头和请求体信息。
	 */
	private void logRequest(RoutingContext context, long startTime) {
		log.info("{}ms: headers={}, body={}", System.currentTimeMillis() - startTime, context.request().headers(),
				context.body() != null ? context.body().asString() : "none");
	}

	/**
	 * Hook for initialization, can be overridden by subclasses. 初始化钩子，子类可覆盖。
	 */
	public void initialized() {
	}
}